<?php
/**
 * 数据库基类
 * @package Db
 * @author Chengxuan <chengxuan@leju.sina.com.cn>
 * @version #Id:db_base.php 2010-4-27 下午06:03:32#
 */
abstract class Cls_Db_Base {
	/**
	 * 默认缓存时间
	 * @var int
	 */
	const CACHE_TIME = 120;
	
	/**
	 * 返回类型（所有）
	 * @var int
	 */
	const FETCH_BOTH = 0;
	
	/**
	 * 返回类型（数字索引）
	 * @var int
	 */
	const FETCH_NUM = 1;
	
	/**
	 * 返回类型（字段索引）
	 * @var int
	 */
	const FETCH_ASSOC = 2;
	
	/**
	 * 操作方式（插入）
	 * @var string
	 */
	const TYPE_INSERT = 'INSERT INTO';
	
	/**
	 * 操作方式（忽略插入）
	 * @var string
	 */
	const TYPE_INSERT_IGNORE = 'INSERT IGNORE INTO';
	
	/**
	 * 操作方式（更新）
	 * @var string
	 */
	const TYPE_UPDATE = 'UPDATE';
	
	/**
	 * 操作方式（替换插入）
	 * @var string
	 */
	const TYPE_REPLACE = 'REPLACE INTO';
	
	/**
	 * 数据库ID（主）
	 * @var int
	 */
	const CONN_MASTER = 1;
	
	/**
	 * 数据库ID（从）
	 * @var int
	 */
	const CONN_SLAVE = 2;
	
	/**
	 * 读锁
	 * @var string
	 */
	const LOCK_READ = 'READ';
	
	/**
	 * 写锁
	 * @var string
	 */
	const LOCK_WRITE = 'WRITE';
	
	
	/**
	 * 最后一条SQL语句
	 * @var string
	 */
	public $sql='';
	
	/**
	 * 错误级别
	 * @var int
	 */
	
	public $display_errors = 7;
	
	/**
	 * 数据库配置（格式参数core/cls/db.php  function:add）
	 * @var array
	 */
	public $conf = array();
	
	/**
	 * 构造方法
	 * @param 数据库连接配置 $conf
	 */
	public function __construct($conf)
	{
		$this->conf = $conf;
	}
	
	/**
	 * 连接数据库
	 * @param string $host
	 * @param string $username
	 * @param string $password
	 * @param string $database
	 * @param string $port
	 */
	abstract public function connect($host, $username, $password, $database, $port=3306);
	
	/**
	 * 查询一句语句，反回查询句柄
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param boolean $is_master		是否强制使用主库
	 * @return mixed
	 */
	abstract public function query($sql, $param=null, $is_master=false);
	
	/**
	 * 查询一条语句，返回影响的数据行数
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param boolean $is_master		是否强制使用主库
	 * @return int
	 */
	abstract public function execute($sql, $param=null, $is_master=false);
	
	/**
	 * 取得错误
	 */
	abstract public function error($statement);
	
	/**
	 * 取得一个数据
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param null/string $column	指定返回字段
	 * @param boolean $is_master	是否强制使用主库
	 * @return string/boolean
	 */
	abstract public function fetchOne($sql, $param=null, $column=null, $is_master=false);
	
	/**
	 * 取得一行数据
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param boolean $is_master	是否强制使用主库
	 * @param int $fetch_type		指定返回数据格式(Cls_Db_Base::FETCH_*)
	 * @return array/boolean
	 */
	abstract public function fetchRow($sql, $param=null, $is_master=false, $fetch_type=self::FETCH_ASSOC);
	
	/**
	 * 取得一列数据
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param null/string $column	指定返回字段
	 * @param boolean $is_master		是否强制使用主库
	 * @return array/boolean
	 */
	abstract public function fetchCol($sql, $param=null, $column=null, $is_master=false);	
	
	/**
	 * 取得所有数据
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param boolean $is_master		是否强制使用主库
	 * @param int $fetch_type		指定返回数据格式(Cls_Db_Base::FETCH_*)
	 * @return array/boolean
	 */
	abstract public function fetchAll($sql, $param=null, $is_master=false, $fetch_type=self::FETCH_ASSOC);

	/**
	 * 取得一个数据（缓存）
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param null/string $column	指定返回字段
	 * @param boolean $is_master		是否强制使用主库
	 * @param int $time				缓存时间
	 * @param null/string $key		指定缓存的键
	 * @return string/boolean
	 */
	public function fetchOneCache($sql, $param=null, $column=null, $is_master=false, $time=self::CACHE_TIME, $key=null)
	{
		!$key && $key = md5('One' . $sql . serialize($param) . $column);
		
		if(($result = Cls_Mc::init()->get_cache($key,$time)) === false){
			$result = $this->fetchOne($sql, $param, $column, $is_master);
			Cls_Mc::init()->set_cache($key,$result,$time);
		}
		return $result;
	}
	
	/**
	 * 取得一行数据（缓存）
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param boolean $is_master		是否强制使用主库
	 * @param int $time				缓存时间
	 * @param null/string $key		指定缓存的键
	 * @param int $fetch_type		指定返回数据格式(Cls_Db_Base::FETCH_*)
	 * @return array/boolean
	 */
	public function fetchRowCache($sql, $param=null, $is_master=false, $time=self::CACHE_TIME, $key=null, $fetch_type=self::FETCH_ASSOC)
	{
		!$key && $key=md5('Row' . $sql . serialize($param) . $fetch_type);
		
		if(($result = Cls_Mc::init()->get_cache($key,$time)) === false){
			$result = $this->fetchRow($sql, $param, $is_master, $fetch_type);
			Cls_Mc::init()->set_cache($key,$result,$time);
		}
		return $result;
	}
	
	/**
	 * 取得一列数据（缓存）
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param null/string $column	指定返回字段
	 * @param boolean $is_master		是否强制使用主库
	 * @param int $time				缓存时间
	 * @param null/string $key		指定缓存的键
	 * @return array/boolean
	 */
	public function fetchColCache($sql, $param=null, $column=null, $is_master=false, $time=self::CACHE_TIME, $key=null)
	{
		!$key && $key=md5('Col' . $sql . serialize($param) . $column);
		
		if(($result = Cls_Mc::init()->get_cache($key,$time)) === false){
			$result = $this->fetchCol($sql, $param, $column, $is_master);
			Cls_Mc::init()->set_cache($key,$result,$time);
		}
		return $result;
	}
	
	/**
	 * 取得所有数据（缓存）
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param boolean $is_master		是否强制使用主库
	 * @param int $time				缓存时间
	 * @param null/string $key		指定缓存的键
	 * @param int $fetch_type		指定返回数据格式(Cls_Db_Base::FETCH_*)
	 * @return array/boolean
	 */
	
	public function fetchAllCache($sql, $param=null, $is_master=false, $time=self::CACHE_TIME, $key=null, $fetch_type=self::FETCH_ASSOC)
	{
		!$key && $key=md5('All' . $sql . serialize($param) . $fetch_type);
		
		if(($result = Cls_Mc::init()->get_cache($key,$time)) === false){
			$result = $this->fetchAll($sql, $param, $is_master, $fetch_type);
			Cls_Mc::init()->set_cache($key,$result,$time);
		}
		return $result;
	}
		
	/**
	 * 测试连接是否存在，如果不存在，重新连接
	 * @param mixed $conn
	 */
	abstract public function ping($conn);
	
	/**
	 * 取得SELECT查询的数据行数
	 * @param mixed $statement 查询句柄
	 */
	abstract public function numRows($statement);
	
	/**
	 * 取得插入的最后一条的ID
	 */
	public function lastId()
	{
		return $this->fetchOne('SELECT LAST_INSERT_ID()', null, null, true);
	}
	
	/**
	 * 锁表
	 * 
	 * <code>
	 * $db->lock(array(
	 * 	'table1'	=> Cls_Db_Base::LOCK_READ,
	 * 	'table2'	=> Cls_Db_Base::LOCK_WRITE,
	 * 	'table3'	=> Cls_Db_Base::LOCK_WRITE,
	 * ));
	 * </code>
	 * @param array $tables	表名及锁定类型
	 */
	public function lock(array $tables)
	{
		$lock = array();
		foreach($tables as $table=>$lock)
		{
			$lock[] = "`{$table}` {$lock}";
		}
		$sql =  'LOCK TABLES ' . implode(',', $lock);
		$this->execute($sql);		
	}
	
	/**
	 * 解除锁表
	 */
	public function unlock()
	{
		$this->execute('UNLOCK TABLES');
	}

	
	/**
	 * 自动执行操作(针对Insert/Update/Replace操作)
	 * @param string $type	(Cls_Db_Base::TYPE_*)
	 * @param string $table
	 * @param array $data
	 * @param string $where
	 * @param array $param
	 * @param string $limit
	 * @return int
	 */
	public function autoExecute($type, $table, $data, $where='', $param='', $limit=null)
	{
		if(!in_array($type, array(self::TYPE_INSERT, self::TYPE_INSERT_IGNORE, self::TYPE_UPDATE, self::TYPE_REPLACE)))
		{
			return false;
		}		
		
		
		//整理新数据
		$set = array();
		foreach ($data as $key=>$value)
		{
			$value = addslashes($value);
			$set[] = "`{$key}` = '{$value}'";
		}
		$set = implode(',', $set);
		unset($data);
		
		$sql = "{$type} `{$table}` SET {$set}";
		
		$where = $this->makeSQL($where, $param);
		$where && $sql .= " WHERE {$where}";
		
		if($limit)
		{
			if(!Cls_Validator::limit($limit)) {
				$this->throwError('LIMIT 字段格式错误');
				return false;
			}
			
			$sql .= " LIMIT {$limit} ";
		}
		return $this->execute($sql);
	}
	
	/**
	 * 生成SQL语句
	 * @param string $sql
	 * @param array $args
	 * @return string
	 */
	protected function makeSQL ($sql, $args=null)
	{		
		if($args)
		{
			foreach($args as $value)
			{
				if(!$offset = strpos($sql, '?'))
				{
					break;
				}
				
				if(is_array($value))
				{
					foreach($value as $key=>$val)
					{
						$value[$key] = '\'' . addslashes($val) . '\'';
					}
					$value = implode(',', $value);
				}
				else
				{
					$value = '\'' . addslashes($value) . '\'';
				}

				$sql = substr($sql, 0, $offset) . $value . substr($sql, $offset+1);
			}
		}
		
		return $sql;
	}
	
	/**
	 * 通过SQL语句，确定使用的服务器
	 * @param string $sql
	 * @param int
	 */
	protected function getServer($sql)
	{
		return substr(strtolower(trim($sql)), 0, 6) == 'select' ? self::CONN_SLAVE : self::CONN_MASTER;
	}
	
	/**
	 * 抛出错误
	 * @param string $string 错误字符串
	 */
	public function throwError($string)
	{
		//将错误写入日志
		if($this->display_errors & 1)	error_log($string);
		
		//将错误显示在页面上		
		if($this->display_errors & 2)	echo $string . "<br />\r\n";
		
		//将错误发送至FirePHP
		if($this->display_errors & 4)	Cls_Debug::error($string, 'DB', Cls_FirePHP::ERROR);
		
		//追踪错误
		if($this->display_errors & 8)	Cls_Debug::trace('DB Error Trace');
		
		//出错终止
		if($this->display_errors & 16)	exit;
	}
	
	/**
	 * 取得数据库账号信息
	 * @param int $server	指定哪台服务器
	 * @return array
	 */
	protected function getConf($server) {
		return isset($this->conf[$server]) ? $this->conf[$server] : array();
	}
	
}


